/*
 * Noise generation for PuTTY's cryptographic random number
 * generator.
 */

#include <stdio.h>

#include "putty.h"
#include "ssh.h"
#include "storage.h"

#include <wincrypt.h>

DECL_WINDOWS_FUNCTION(static,
                      BOOL,
                      CryptAcquireContextA,
                      (HCRYPTPROV *, LPCTSTR, LPCTSTR, DWORD, DWORD));
DECL_WINDOWS_FUNCTION(static,
                      BOOL,
                      CryptGenRandom,
                      (HCRYPTPROV, DWORD, BYTE *));
DECL_WINDOWS_FUNCTION(static, BOOL, CryptReleaseContext, (HCRYPTPROV, DWORD));
static HMODULE wincrypt_module = NULL;

/*
 * This function is called once, at PuTTY startup.
 */

void noise_get_heavy(void (*func)(void *, int))
{
  HANDLE srch;
  WIN32_FIND_DATA finddata;
  DWORD pid;
  HCRYPTPROV crypt_provider;
  char winpath[MAX_PATH + 3];

  GetWindowsDirectory(winpath, sizeof(winpath));
  strcat(winpath, "\\*");
  srch = FindFirstFile(winpath, &finddata);
  if (srch != INVALID_HANDLE_VALUE) {
    do {
      func(&finddata, sizeof(finddata));
    } while (FindNextFile(srch, &finddata));
    FindClose(srch);
  }

  pid = GetCurrentProcessId();
  func(&pid, sizeof(pid));

  if (!wincrypt_module) {
    wincrypt_module = load_system32_dll("advapi32.dll");
    GET_WINDOWS_FUNCTION(wincrypt_module, CryptAcquireContextA);
    GET_WINDOWS_FUNCTION(wincrypt_module, CryptGenRandom);
    GET_WINDOWS_FUNCTION(wincrypt_module, CryptReleaseContext);
  }

  if (wincrypt_module && p_CryptAcquireContextA && p_CryptGenRandom &&
      p_CryptReleaseContext &&
      p_CryptAcquireContextA(
          &crypt_provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
    BYTE buf[32];
    if (p_CryptGenRandom(crypt_provider, 32, buf)) {
      func(buf, sizeof(buf));
    }
    p_CryptReleaseContext(crypt_provider, 0);
  }

  read_random_seed(func);
  /* Update the seed immediately, in case another instance uses it. */
  random_save_seed();
}

void random_save_seed(void)
{
  int len;
  void *data;

  if (random_active) {
    random_get_savedata(&data, &len);
    write_random_seed(data, len);
    sfree(data);
  }
}

/*
 * This function is called every time the random pool needs
 * stirring, and will acquire the system time in all available
 * forms.
 */
void noise_get_light(void (*func)(void *, int))
{
  SYSTEMTIME systime;
  DWORD adjust[2];
  BOOL rubbish;

  GetSystemTime(&systime);
  func(&systime, sizeof(systime));

  GetSystemTimeAdjustment(&adjust[0], &adjust[1], &rubbish);
  func(&adjust, sizeof(adjust));
}

/*
 * This function is called on a timer, and it will monitor
 * frequently changing quantities such as the state of physical and
 * virtual memory, the state of the process's message queue, which
 * window is in the foreground, which owns the clipboard, etc.
 */
void noise_regular(void)
{
  HWND w;
  DWORD z;
  POINT pt;
  MEMORYSTATUS memstat;
  FILETIME times[4];

  w = GetForegroundWindow();
  random_add_noise(&w, sizeof(w));
  w = GetCapture();
  random_add_noise(&w, sizeof(w));
  w = GetClipboardOwner();
  random_add_noise(&w, sizeof(w));
  z = GetQueueStatus(QS_ALLEVENTS);
  random_add_noise(&z, sizeof(z));

  GetCursorPos(&pt);
  random_add_noise(&pt, sizeof(pt));

  GlobalMemoryStatus(&memstat);
  random_add_noise(&memstat, sizeof(memstat));

  GetThreadTimes(GetCurrentThread(), times, times + 1, times + 2, times + 3);
  random_add_noise(&times, sizeof(times));
  GetProcessTimes(GetCurrentProcess(), times, times + 1, times + 2, times + 3);
  random_add_noise(&times, sizeof(times));
}

/*
 * This function is called on every keypress or mouse move, and
 * will add the current Windows time and performance monitor
 * counter to the noise pool. It gets the scan code or mouse
 * position passed in.
 */
void noise_ultralight(unsigned long data)
{
  DWORD wintime;
  LARGE_INTEGER perftime;

  random_add_noise(&data, sizeof(DWORD));

  wintime = GetTickCount();
  random_add_noise(&wintime, sizeof(DWORD));

  if (QueryPerformanceCounter(&perftime))
    random_add_noise(&perftime, sizeof(perftime));
}
